Skip to content

Sql项目管理 - #6170

Open
tianyifeng-druid wants to merge 12 commits into
t8y2:mainfrom
tianyifeng-druid:sql项目管理

Hidden character warning

The head ref may contain hidden characters: "sql\u9879\u76ee\u7ba1\u7406"
Open

Sql项目管理#6170
tianyifeng-druid wants to merge 12 commits into
t8y2:mainfrom
tianyifeng-druid:sql项目管理

Conversation

@tianyifeng-druid

Copy link
Copy Markdown
Contributor

变更说明

实现 SQL 项目管理 Wave 2 功能,并修复 PR review 反馈的路径安全和 watcher 性能问题。

Wave 2 功能:

  • 新增 projectStore 和 ProjectSettingsDialog ,支持项目树管理(创建/绑定/同步)

  • 新增 SqlFileHistoryDialog ,支持本地文件版本历史查看

  • 新增 sql_project 命令(core + tauri),覆盖项目打开/绑定/同步/文件操作

  • 增强 SqlFilePanel :项目树展示、历史对话框、外部变更检测、内联创建/重命名/删除

  • openExternalSqlFile 支持 catalog 参数传递
    安全修正(PR review 阻塞项):

  • 新增 canonicalize_within_root :在词法校验之后对目标路径做真实路径 canonicalize(不存在时回溯到最近父目录),防止 symlink 越界。已接入 5 个文件操作命令, rename 同时校验 source 和 target

  • 提取 useFolderWatcherLifecycle composable:仅激活项目创建 recursive watcher,非激活项目仅加载树不 watch;切换项目自动拆除/创建 watcher;窗口聚焦仅重扫激活项目

  • trash 依赖版本从 "5" 锁定为 "5.2"

变更类型

  • 新功能
  • Bug 修复
  • 性能优化
  • 代码重构

涉及前端

  • 本 PR 涉及前端改动,已附截图/录屏(见下方) 主要 UI 变更:项目树面板(文件/文件夹展示、内联创建/重命名、右键菜单)、项目设置对话框、文件历史对话框。Watcher 生命周期改造为内部逻辑,无可见 UI 差异。

验证

  • make check 通过
  • make cargo-check-fast 通过
  • cargo check --no-default-features 编译通过
  • vue-tsc --noEmit 零错误
  • vitest run useFolderWatcherLifecycle.spec.ts — 15/15 passed
  • Rust symlink 回归测试(7 用例)— 编译通过,测试二进制因 Windows Tauri DLL 运行时问题需 CI 验证

测试覆盖

测试文件 用例 场景 sql_project.rs #[cfg(test)] 7 symlink 越界:创建/已存在/重命名/删除/正常路径/不存在目标/嵌套 useFolderWatcherLifecycle.spec.ts 15 watcher 生命周期:init/切换/焦点/清理/连续切换

关联 Issue

Close #5841

@github-actions github-actions Bot added area/core Shared DBX core runtime area/desktop Desktop application or Tauri shell area/docs Documentation site or repository documentation dependencies/backend Adds a backend dependency ui-change Changes user-visible interface, text, or visual assets labels Aug 13, 2026

@t8y2 t8y2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢贡献。当前实现还存在几项需要在合并前解决的问题:

  1. 后端没有建立可信项目路径边界。创建、重命名、删除和快照命令直接接受前端传入的 root_path 或绝对路径,没有根据已保存的项目记录和 trusted 状态重新校验。项目信任不能只依赖 UI,否则被篡改的调用仍可操作任意路径。
  2. canonicalize 后的目标路径被直接用于重命名和删除。项目内符号链接指向项目内文件时,用户操作链接会实际重命名或删除链接目标。canonical path 应只用于边界验证,实际文件操作应保留 lexical path,并补内部 symlink 测试。
  3. watcher 创建存在异步竞态。项目切换或重复初始化发生在 watch() 返回前时,旧 watcher 无法被清理,随后还可能覆盖新的 unwatch handle,造成 watcher 泄漏和重复事件。
  4. 项目路径无条件转小写会在大小写敏感文件系统上把不同目录视为同一项目,可能关联到错误的历史快照。
  5. 当前 head 仍有 Rust 测试、fmt/clippy、前端 source-contract 和 Nix 检查失败,并且与最新 main 存在冲突。

这些问题分别涉及后端权限边界、文件系统语义、异步资源生命周期和跨平台路径身份,不适合维护者做局部代修。请在作者分支中完成修复并增加对应回归测试后再请求审查。

- Add canonicalize_within_root to detect symlink traversal; wire into 5 file operation commands

- Add 7 symlink regression tests (create, existing file, rename, delete, normal path, nonexistent target, nested)

- Extract useFolderWatcherLifecycle composable: only active project watches, switch drops old/creates new, focus rescans active only

- Replace full-project watcher logic in SqlFilePanel.vue with lifecycle composable

- Add 15 unit tests for watcher lifecycle (init, switch, focus, cleanup)

- Pin trash dependency to 5.2
- Resolve file-op root from project_id via find_sql_project_by_id, rejecting untrusted projects

- Replace canonicalize_within_root with validate_within_root (validation only); file ops use lexical paths

- Use create_new/create_dir atomics to close the TOCTOU window between validation and write

- Add internal symlink regression tests

- Fix watcher async race with pendingWatchers set

- Normalize root path case-insensitively on Windows only
@tianyifeng-druid

Copy link
Copy Markdown
Contributor Author

我再检查检查~~~~

@t8y2 t8y2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本轮已经修复了 canonical path 被用于实际 symlink 操作、路径无条件小写和当前合并冲突,但仍有三个问题需要闭环。

  1. src-tauri/src/commands/sql_project.rs:123save_sql_project 仍接受并持久化前端提供的完整 SqlProject,包括 root_pathtrustedcrates/dbx-core/src/storage.rs:3207 的 UPSERT 也直接写入这些字段。调用方可以伪造任意可信项目,从而绕过新增的 resolve_trusted_root。请拆分项目元数据更新与后端信任流程,普通保存不得修改 id/root_path/trusted/created_at,并增加伪造 payload 的回归测试。

  2. 当前仍是先 canonicalize 校验,再通过 lexical path 执行 read/create/rename/trash。create_new 只能保护最终路径组件,无法阻止父目录在校验后被替换成外部 symlink,因此 TOCTOU 窗口没有真正关闭。这里需要基于已打开项目根目录句柄执行逐组件 no-follow 或 descriptor-relative 文件操作,不能依赖“校验后再按字符串操作”。

  3. apps/desktop/src/composables/useFolderWatcherLifecycle.ts:24 的 watcher 生命周期仍以 ensureWatcher(path): void fire-and-forget。若项目切换发生在异步 watcher 创建完成前,生命周期层无法释放晚到的 unwatch handle。请让初始化返回 handle,增加 generation/cancellation,并用 deferred watcher 初始化覆盖 A → B 连续切换。

另外,当前 exact head 的 frontendrust-fmt-clippyrust-testnix-packaging checks 仍失败,请在同一 head 上修复并跑绿后再请求复审。

Replace save_sql_project full-object UPSERT with insert/update/trust so the frontend cannot forge root_path/trusted/created_at. Run project file ops through cap-std descriptor-relative handles instead of canonicalize-then-lexical-path. Return unwatch handle with generation/cancellation in the watcher lifecycle. Fix std::os::unix::fs::symlink path and queryStore catalog argument.
externalSqlFileTargetPersistence.test.ts now asserts the meta-object catalog field instead of the removed positional argument; list_sql_files.rs compares names character-by-character so proc.sql sorts before proc1.sql.
# Conflicts:
#	apps/desktop/src/components/layout/SqlFilePanel.vue

@t8y2 t8y2 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request changes:当前 exact head 仍有几项会影响数据安全和项目语义的 blocker。

  1. 项目绑定的连接只作为文件历史 target 的 fallback;旧 connection/database/catalog 会优先,OS 打开和 QuickOpen 甚至不读取项目绑定,defaultSchema 也从未注入 tab。项目 SQL 可能在错误数据源执行,请统一所有入口,以项目上下文为权威并补跨连接/catalog 测试。
  2. resolve_trusted_root 会重新 canonicalize 已信任路径并用 ambient authority 打开。如果原根目录被替换成指向外部的 symlink/junction,create/rename/delete/trash 就会落到项目外。请绑定并验证稳定的根目录 identity,覆盖根目录替换测试。
  3. 删除前把条目改名到项目根目录再调用 trash,系统回收站记录的 original path 也变成 .dbx-trash-*;Put Back 无法恢复原父目录和原名称。这个安全与恢复语义需要平台级方案或明确产品设计。
  4. 遗留文件夹迁移在单项失败后仍清空整个旧列表;文件/目录重命名又不会迁移 snapshot path,都会让用户配置或本地历史失联。
  5. Local History 会一次加载最多 8 MiB × 20 = 160 MiB 内容,SqlFilePanel 还会并发递归扫描全部项目;请改为按需加载并补大数据量验证。

远端精确 head 上 262 个聚焦前端测试、17 个聚焦 Rust 测试、typecheck、cargo fmt、cargo check 均通过,GitHub checks 也全绿,但现有测试未覆盖上述行为。

# Conflicts:
#	apps/desktop/src/composables/useFileDrop.ts
#	src-tauri/capabilities/default.json
- treat bound project connection/schema as authoritative at all open entry points
- bind and verify stable root identity to prevent symlink escape
- keep failed legacy folder migrations; migrate snapshot paths on rename
- lazy-load Local History snapshots and only scan the active project
- replace system trash with DBX-managed trash (restore/empty with rollback)
…test

cap-std's Dir::try_exists returns Ok(false) for a missing path instead of
Err, so the old is_err() assertions always failed on Linux CI. Assert the
handle remains usable by expecting false for a nonexistent entry.
…e-and-recreate

(dev, ino) alone cannot detect same-path delete-and-recreate: Linux ext4
reuses inodes, so a recreated directory matches the recorded identity. Write
a hidden marker file (.dbx-root-identity, random token) inside the project
root when establishing identity and verify it on every resolve; a recreated
or replaced root loses the marker and is rejected deterministically.

- RootIdentity gains marker field (serde skip when None, legacy rows keep
  working); no DB migration needed (identity is stored as JSON)
- read_project_root_identity establishes/reuses the marker (read-only roots
  degrade to marker-less identity)
- root_identity_matches() checks (dev, ino)+fallback then marker content
- new test resolve_trusted_root_rejects_deleted_marker
- make delete-and-recreate test deterministic (no longer inode-reuse flaky)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Shared DBX core runtime area/desktop Desktop application or Tauri shell area/docs Documentation site or repository documentation dependencies/backend Adds a backend dependency ui-change Changes user-visible interface, text, or visual assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 参考datagrip实现项目管理类功能(独立的存储过程/sql项目管理)

2 participants